package br.org.bertol.mestrado.engine.optimisation.sls;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;

import org.apache.log4j.Logger;

import br.org.bertol.mestrado.engine.Objective;
import br.org.bertol.mestrado.engine.exception.HeuristicsException;
import br.org.bertol.mestrado.engine.optimisation.entity.AbstractUnit;
import br.org.bertol.mestrado.engine.optimisation.moo.ParetoDominace;

/**
 * @author contaqualquer
 * @param <U>
 */
public final class VariableNeighborhoodSearch<T, U extends AbstractUnit<T>>
        extends AbstractStochasticLocalSearch<T, U> {

    /***/
    private final transient int maxNeighborhood;

    /**
     * Cria um objeto VNS.
     * @param startUnit
     * @param numIteracoes
     */
    public VariableNeighborhoodSearch(
        final U startUnit,
        final long numIteracoes, final ArrayList<T> classes,
        final Objective[] objectives) {
        super(numIteracoes, startUnit, classes, objectives);

        this.maxNeighborhood = classes.size() / 2;

    }

    @SuppressWarnings("unchecked")
    @Override
    public void evaluate() throws HeuristicsException {

        try {
            betterUnit = (U) startUnit.clone();

            Logger.getLogger(getClass()).info("ILS START : " + startUnit);

            int vnsControl = 1;

            int iteracoes = 0;

            int bestIeration = 0;

            // loop principal
            while (iteracoes - bestIeration < numIteracoes) {
                iteracoes++;

                vnsControl = 1;

                while (vnsControl <= maxNeighborhood) {

                    U candidatoS = (U) betterUnit.clone();

                    generateKesimoNeighbor(candidatoS, vnsControl);

                    if (candidatoS.calcFitness()) {

                        hillClimbing.setPath(candidatoS);

                        hillClimbing.runHillClimbing();

                        final U candidatoSs = hillClimbing.getBestPath();

                        if (candidatoS.calcFitness()
                                && paretoMinMinDominace.compare(candidatoSs
                                        .getHeight(),
                                                                candidatoS
                                        .getHeight()) != ParetoDominace.RIGHT_DOMINE) {

                            betterUnit = (U) candidatoSs.clone();

                            candidatoS = (U) candidatoSs.clone();

                            bestIeration = iteracoes;

                            vnsControl = 1;

                            Logger.getLogger(getClass())
                                    .debug(
                                           "Caminho melhor encontrado => "
                                    + candidatoSs);

                        } else {
                            vnsControl++;
                        }

                        hillClimbing.reset();
                    }
                }
            }

            Logger.getLogger(getClass()).info("VNS STOP : " + betterUnit);
        } catch (Exception e) {
            throw new HeuristicsException(e);
        }

    }

    /**
     * Retorna k-ésimo vizinho.
     * @param candidatoS
     *            Solução iniciaçl
     * @param keSimo
     *            range de busca de vizinho
     */
    private void generateKesimoNeighbor(final U candidatoS, final int keSimo) {

        for (int i = 0; i < keSimo; i++) {

            final int pointer1 = (int) (Math.random() * classes
                    .size());

            int pointer2 = pointer1;

            while (pointer1 == pointer2) {
                pointer2 = (int) (Math.random() * classes.size());
            }

            Collections.swap(candidatoS.getRoute(), pointer1, pointer2);
        }
    }

}
